from flask import Flask
from flask import Blueprint, make_response, jsonify
from flask.json.provider import DefaultJSONProvider

from inspect import getmembers
from pathlib import Path
import decimal
import datetime
import logging
from logging.handlers import RotatingFileHandler
import sys

from api.extension import db, migrate, login_manager, celery
import api.view.entry

from api.model.test_model import UserDO

# 项目根目录
BASE_DIR = Path(__file__).resolve().parent.parent

# 回调函数用于通过 session 中存储的用户 ID 重新加载用户对象。
# 它应该接收用户的 unicode ID，并返回相应的用户对象。
# 如果 ID 无效，函数应该返回 None。
@login_manager.user_loader
def load_user(user_id):
    """Load user by ID."""
    print(f"===========> 检索用户方法被调用了,用户ID是 {user_id}...")
    return UserDO.query.get(int(user_id))


# 自定义json数据处理，重写父类的default()方法
class MyJSONEncoder(DefaultJSONProvider):

    def __init__(self, app):
        self.ensure_ascii = False
        self.mimetype = 'application/json; charset=utf-8'
        super().__init__(app)
    
    def default(self, o):
        if isinstance(o, (decimal.Decimal, datetime.date, datetime.time)):
            return str(o)

        if isinstance(o, datetime.datetime):
            return o.strftime("%Y-%m-%d %H:%M:%S")

        return o
    

# app的工厂方法
def create_app(config_object="settings"):  
    """Create Flask app."""
    app = Flask(__name__.split(".")[0])

    # 加载配置
    app.config.from_object(config_object)
    app.json = MyJSONEncoder(app)

    register_extensions(app)
    register_blueprints(app)
    configure_logger(app)
    register_error_handlers(app)


     ##########################################################################

    # from api.view.test_model_api import UserResource, UserResourceWithId, DepartmentResource, DepartmentResourceWithId
    # from api.view.test_model_api import StudentResource, StudentResourceWithId, CourseResource, CourseResourceWithId
    # from api.view.test_model_api import CourseScoreResource, CourseScoreResourceWithId
    # from flask_restful import Api

    # 注册一个蓝图模块，统一url前缀
    # blueprint_test = Blueprint("test_api", __name__, url_prefix="/api/v1/test")
    # test_rest = Api(blueprint_test)

    # 添加我们的视图(就是我们写的接口)
    # test_rest.add_resource(UserResource, *UserResource.url_prefix)
    # test_rest.add_resource(UserResourceWithId, *UserResourceWithId.url_prefix)
     
    # test_rest.add_resource(DepartmentResource, *DepartmentResource.url_prefix)
    # test_rest.add_resource(DepartmentResourceWithId, *DepartmentResourceWithId.url_prefix)
     
    # test_rest.add_resource(StudentResource, *StudentResource.url_prefix)
    # test_rest.add_resource(StudentResourceWithId, *StudentResourceWithId.url_prefix)
     
    # test_rest.add_resource(CourseResource, *CourseResource.url_prefix)
    # test_rest.add_resource(CourseResourceWithId, *CourseResourceWithId.url_prefix)
     
    # test_rest.add_resource(CourseScoreResource, *CourseScoreResource.url_prefix)
    # test_rest.add_resource(CourseScoreResourceWithId, *CourseScoreResourceWithId.url_prefix)

    # 手动注册蓝图到flask实例app中
    # app.register_blueprint(blueprint_test)
   
    ##########################################################################

    # 打印所有的 url路由
    print(app.url_map)
    return app


def register_error_handlers(app: Flask):
    """Register error handlers."""

    def render_error(error):
        """Render error template."""

        # 标准库，用于提取、格式化和打印异常的堆栈跟踪信息，更快地定位问题所在
        import traceback
        # 返回当前被处理的异常的堆栈跟踪字符串表示形式，类似于 print_exc() 的输出但作为字符串返回
        app.logger.error(traceback.format_exc())
        error_code = getattr(error, 'code', 500)
        if not str(error_code).isdigit():
            error_code = 400
        
        return make_response(jsonify(message=str(error)), error_code)

    for errcode in app.config.get("ERROR_CODES") or [400, 401, 403, 404, 405, 500, 502]:
        app.errorhandler(errcode)(render_error)

    app.handle_exception = render_error


def configure_logger(app: Flask):
    """Configure loggers."""
    handler = logging.StreamHandler(sys.stdout)
    formatter = logging.Formatter("%(asctime)s %(levelname)s %(pathname)s %(lineno)d - %(message)s")

    # 如果是debug模式，则把日志输出到控制台
    if app.debug:
        handler.setFormatter(formatter)
        app.logger.addHandler(handler)

    # 同时，如果存在自定义日志文件位置，则也把日志写入到指定的日志文件内
    log_file = app.config['LOG_PATH']
    if log_file and log_file != '/dev/stdout':
        file_handler = RotatingFileHandler(log_file, maxBytes=10 * 1024 * 1024, backupCount=7, encoding='utf-8')
        file_handler.setLevel(getattr(logging, app.config['LOG_LEVEL']))
        file_handler.setFormatter(formatter)
        app.logger.addHandler(file_handler)
    
    app.logger.setLevel(getattr(logging, app.config['LOG_LEVEL']))


# 动态注册一个文件中的蓝图，文件中有多个蓝图实例
def register_blueprints(app: Flask):
     for item in getmembers(api.view.entry):
          if item[0].startswith('blueprint') and isinstance(item[1], Blueprint):
               app.register_blueprint(item[1])


def register_extensions(app: Flask):
    """Register Flask extensions."""

    db.init_app(app)
    migrate.init_app(app, db, directory=f"{BASE_DIR}/migrations")
    login_manager.init_app(app)

    app.config.update(app.config.get("CELERY"))
    celery.conf.update(app.config)

    
